import dayjs from 'dayjs';
import Decimal from 'decimal.js';
import { cloneDeep } from 'lodash';
import numeral from 'numeral';
import { calcITVolume } from './calc';
import { formatStock } from './format';

export type TenTrack = {
  time?: string;
  S5P?: number | string;
  S5V?: number | string;
  S4P?: number | string;
  S4V?: number | string;
  S3P?: number | string;
  S3V?: number | string;
  S2P?: number | string;
  S2V?: number | string;
  S1P?: number | string;
  S1V?: number | string;
  B5P?: number | string;
  B5V?: number | string;
  B4P?: number | string;
  B4V?: number | string;
  B3P?: number | string;
  B3V?: number | string;
  B2P?: number | string;
  B2V?: number | string;
  B1P?: number | string;
  B1V?: number | string;
  newest?: number | string;
  high?: number | string; // 最高价
  low?: number | string; // 最低价
  open?: number | string; // 开盘价
  volume?: number | string; // 成交量
  amount?: number | string; // 成交额
  outer?: number | string; // 外盘
  LU?: number | string; // limit up 涨停
  LD?: number | string; // Limit down 跌停
  inner?: number | string; // 内盘
  chg?: number | string;
  change?: number | string;
  [key: string]: any;
};

export enum QuotationType {
  Up = 'Up',
  Flat = 'Flat',
  Down = 'Down',
}

const MorningStartTime: string = '09:30:00'; //  早盘开盘时间
const MorningEndTime: string = '11:30:00'; //  早盘闭盘时间
const MiddayStartTime: string = '13:00:00'; //  午盘盘时间
const MiddayEndTime: string = '14:57:00'; //  午盘闭盘时间

export enum UpDownType {
  Up = 'Up',
  Down = 'Down',
  Flat = 'Flat',
}

//  买卖5档 对应计算压单系数的倍率
const RankMultipleMap: any = {
  1: 2,
  2: 1.75,
  3: 1.5,
  4: 1.25,
  5: 1,
};

const calcTP = (price: string | number, volume: string | number) => {
  return price && volume && price !== '-' && volume !== '-'
    ? new Decimal(price).mul(new Decimal(volume)).mul(100).toNumber()
    : '-';
};

const calcTotal = (tenTrack) => {
  let VOBO: number = 0; //  买单总量
  let VOSO: number = 0; //  卖单总量
  let TPOBO: number = 0; //  买单总价
  let TPOSO: number = 0; //  卖单总价
  //  eslint-disable-next-line padding-line-between-statements
  Object.keys(tenTrack).forEach((key: string) => {
    const sExp = /S\dV/g;
    const bExp = /B\dV/g;

    if (sExp.test(key)) {
      VOSO += +(tenTrack[key] && tenTrack[key] === '-' ? 0 : tenTrack[key]);

      const tpn = tenTrack?.[key.replace('V', 'TP')];

      if (tpn && tpn !== '-') {
        TPOSO += +tpn;
      }
    }

    if (bExp.test(key)) {
      VOBO += +(tenTrack[key] && tenTrack[key] === '-' ? 0 : tenTrack[key]);

      const tpn = tenTrack?.[key.replace('V', 'TP')];

      if (tpn && tpn !== '-') {
        TPOBO += +tpn;
      }
    }
  });

  return {
    VOBO,
    TPOBO,
    VOSO,
    TPOSO,
  };
};

// CA 集合竞价 call auction
// IT 盘中交易 intraday trading

export default class Analyze {
  private originData: any[]; // 原始数据
  private hqMap: any = {}; // 行情数据
  private fsMap: any = {}; // 分时数据
  private hqTimes: string[] = [];
  private fsTimes: string[] = [];
  private times: string[] = [];
  private dataToBeCleaned: any = {}; // 待清洗数据
  private CATradingData: any = {};
  count: number = 0; // 统计总数

  shippingOrderCount: number = 0; // 卖单压单次数  5倍平均分时成交量
  billOfLadingCount: number = 0; // 买单托单次数
  shippingOrder10TCount: number = 0; // 卖单压单次数  10倍 及以上
  billOfLading10TCount: number = 0; // 买单托单次数 10倍 及以上

  outerMoreThanInnerCount: number = 0; // 外盘大于内盘数
  innerMoreThanOuterCount: number = 0; // 内盘大于外盘数

  totalBuyAmount: number = 0; // 买单总价
  totalSellAmount: number = 0; // 卖单总价
  avgBuyAmount: number = 0; // 买单均价
  avgSellAmount: number = 0; // 卖单均价

  //  private statistics: any = {
  //    mainBuyOrderNum: 0,
  //    mainSellOrderNum: 0,
  //    commonOrderNum: 0,
  //  };

  private marketSnapshot: TenTrack[] = [];

  constructor({ originData }: { originData: any[] }) {
    this.originData = originData;

    const priceStatistic: any = {};

    originData?.forEach((item: any, index: number) => {
      if (item?.details) {
        const details = item?.details?.map((detail: string) => detail.split(','));

        details?.forEach((detail: any) => {
          const [time, price, volume, num, type] = detail;

          this.fsMap[time] = {
            time,
            price,
            volume,
            num,
            type,
            originIndex: index,
          };
        });
      } else {
        const stock = formatStock(item);

        if (stock?.Hms) {
          this.hqMap[stock?.Hms] = { ...stock, originIndex: index };
        } else {
          this.dataToBeCleaned[dayjs(item?.time).format('HH:mm:ss')] = {
            ...stock,
            originIndex: index,
          };
        }

        if (stock?.newest) {
          if (!priceStatistic[stock?.newest]) {
            priceStatistic[stock?.newest] = 1;
          } else {
            priceStatistic[stock?.newest] += 1;
          }
        }
      }
    });

    this.hqTimes = Object.keys(this.hqMap);
    this.fsTimes = Object.keys(this.fsMap);
    this.cleanseHQ();
    this.times = this.hqTimes.filter((time: string) => this.fsTimes.includes(time));
    this.generateMarketSnapshot();
    this.statistics();
  }

  // 清洗数据
  cleanseHQ() {
    const extraTimes = this.fsTimes.filter((fsTime: string) => {
      return !this.hqTimes.includes(fsTime);
    });

    Object.keys(this.dataToBeCleaned).forEach((key) => {
      const filterTimes = extraTimes.filter(
        (time: string) => time < key && dayjs(key, 'HH:mm:ss').valueOf() - dayjs(time, 'HH:mm:ss').valueOf() <= 6000,
      );

      if (filterTimes?.length) {
        this.hqMap[filterTimes[filterTimes?.length - 1]] = this.dataToBeCleaned[key];
      } else {
        this.hqMap[key] = this.dataToBeCleaned[key];
      }

      delete this.dataToBeCleaned[key];
    });
  }

  generateMarketSnapshot() {
    let tenTrack: TenTrack & { details?: any[] } = {};

    const setTenTrack = (stock: TenTrack) => {
      const genTrack = (type: 'S' | 'B', num: number) => {
        const pKey = [type, num, 'P'].join(''),
          vKey = [type, num, 'V'].join(''),
          tpKey = [type, num, 'TP'].join('');

        const price = stock?.[pKey] ?? tenTrack?.[pKey] ?? '-',
          volume = stock?.[vKey] ?? tenTrack?.[vKey] ?? '-';

        return {
          [pKey]: price,
          [vKey]: volume,
          [tpKey]: calcTP(price, volume),
        };
      };

      tenTrack = {
        time: stock?.time ?? '-',
        ...genTrack('S', 5),
        ...genTrack('S', 4),
        ...genTrack('S', 3),
        ...genTrack('S', 2),
        ...genTrack('S', 1),

        ...genTrack('B', 1),
        ...genTrack('B', 2),
        ...genTrack('B', 3),
        ...genTrack('B', 4),
        ...genTrack('B', 5),

        newest: stock?.newest ?? tenTrack?.newest ?? '-',
        high: stock?.high ?? tenTrack?.high ?? '-',
        low: stock?.low ?? tenTrack?.low ?? '-',
        open: stock?.open ?? tenTrack?.open ?? '-',
        volume: stock?.volume ?? tenTrack?.volume ?? '-',
        amount: stock?.amount ?? tenTrack?.amount ?? '-',
        outer: stock?.outer ?? tenTrack?.outer ?? '-',
        LU: stock?.LU ?? tenTrack?.LU ?? '-',
        LD: stock?.LD ?? tenTrack?.LD ?? '-',
        inner: stock?.inner ?? tenTrack?.inner ?? '-',
        prevClose: stock?.prevClose ?? tenTrack?.prevClose ?? '-',
        chg: stock?.chg ?? tenTrack?.chg ?? '-',
        change: stock?.change ?? tenTrack?.change ?? '-',
        turn: stock?.turn ?? tenTrack?.turn ?? '-',
        Hms: stock?.Hms,
      };
    };

    let prevTenTrack: TenTrack;
    let prevSnapshot: any;

    let hqIndex: number = 0;

    Object.keys(this.hqMap)
      .sort((prev, next) => {
        if (prev < next) {
          return -1;
        } else {
          return 1;
        }
      })
      .forEach((key) => {
        setTenTrack(this.hqMap[key]);

        if (key < MorningStartTime) {
          this.CATradingData = {
            inner: tenTrack?.inner,
            outer: tenTrack?.outer,
            //  volume: +tenTrack?.inner + tenTrack?.outer,
          };
        }

        if ((key >= MorningStartTime && key <= MorningEndTime) || (key >= MiddayStartTime && key <= MiddayEndTime)) {
          const ITInner = calcITVolume({ tenTrack, prevTenTrack, key: 'inner', CATradingData: this.CATradingData });
          const ITOuter = calcITVolume({ tenTrack, prevTenTrack, key: 'outer', CATradingData: this.CATradingData });

          if (ITInner > ITOuter) {
            this.innerMoreThanOuterCount += 1;
          } else if (ITOuter > ITInner) {
            this.outerMoreThanInnerCount += 1;
          }

          const details: any = [];

          let fsVolume: number = 0;

          Object.keys(this.fsMap)
            .filter((time) => {
              return time >= MorningStartTime && time <= key;
            })
            .forEach((key) => {
              fsVolume += +this.fsMap?.[key].volume;
              details.push(this.fsMap?.[key]);
              delete this.fsMap[key];
            });

          const quantityDiff = fsVolume - ITInner - ITOuter;
          const surplus = quantityDiff === 0 ? 0 : quantityDiff + (prevTenTrack?.surplus || 0);
          const diff = prevTenTrack ? { ...this.compareDiff(prevTenTrack, tenTrack) } : undefined;

          const { VOBO, TPOBO, VOSO, TPOSO } = calcTotal(tenTrack);

          const snapshot = cloneDeep({
            ...tenTrack,
            CAInner: this.CATradingData.inner,
            CAOuter: this.CATradingData.outer,
            ITInner,
            ITOuter,
            details,
            fsVolume,
            surplus,
            hqIndex,
            diff,
            quotationType:
              tenTrack?.newest !== tenTrack?.prevClose
                ? tenTrack?.newest > tenTrack?.prevClose
                  ? QuotationType.Up
                  : QuotationType.Down
                : QuotationType.Flat,
            VOBO,
            TPOBO,
            VOSO,
            TPOSO,
          });

          this.analyzeIntent(prevSnapshot, snapshot);

          Object.assign(snapshot, {
            ...this.compareSnapshot(prevSnapshot, snapshot),
          });
          this.marketSnapshot.push(snapshot);

          this.totalBuyAmount += +TPOBO;
          this.avgBuyAmount = this.totalBuyAmount / this.count;
          this.totalSellAmount += +TPOSO;
          this.avgSellAmount = this.totalSellAmount / this.count;

          prevTenTrack = { ...tenTrack, surplus, hqIndex };
          prevSnapshot = snapshot;
          ++hqIndex;
        }
      });
  }

  getMarketSnapshot() {
    return this.marketSnapshot;
  }

  compareDiff(prevTenTrack: TenTrack, tenTrack: TenTrack) {
    //  this.compareTenTrack(prevTenTrack, tenTrack);
    const inner = Number(tenTrack?.inner) - Number(prevTenTrack?.inner);
    const outer = Number(tenTrack?.outer) - Number(prevTenTrack?.outer);
    const { newly, still, disappeared } = this.compareTenTrack(prevTenTrack, tenTrack);

    return {
      inner,
      outer,
      still,
      newly,
      disappeared,
    };
  }

  compareTenTrack(prevTenTrack: TenTrack, tenTrack: TenTrack) {
    const formatToMap = (track: TenTrack) => {
      const exp = /(S|B)|\dP/g;
      const map: any = {};

      Object.keys(track).forEach((key: string) => {
        const matches = key.match(exp);

        if (matches?.length === 2) {
          map[track?.[key]] = {
            price: track?.[key],
            count: track?.[key.replace('P', 'V')],
            type: matches?.[0],
            key,
          };
        }
      });

      return map;
    };

    const preTrackMap: any = formatToMap(cloneDeep(prevTenTrack));

    const trackMap: any = formatToMap(cloneDeep(tenTrack));

    const still: any = {};
    const disappeared: any = {};
    const newly: any = {};

    Object.keys(preTrackMap).forEach((price: string) => {
      const { count, type, key } = preTrackMap[price];

      if (trackMap?.[price] && trackMap?.[price]?.type === type) {
        still[trackMap?.[price]?.key] = {
          ...trackMap?.[price],
          price,
          count: trackMap?.[price]?.count - count,
        };
        delete trackMap?.[price];
      } else {
        disappeared[key] = preTrackMap[price];
      }
    });
    Object.keys(trackMap).forEach((price: string) => {
      newly[trackMap[price].key] = { ...trackMap[price] };
    });

    return {
      newly, // 新出现的
      still, // 还存在的
      disappeared, // 消失的
    };
  }

  compareSnapshot(prevSnapshot: any, snapshot: any) {
    if (prevSnapshot) {
      const upDownType =
        snapshot.newest > prevSnapshot.newest
          ? UpDownType.Up
          : snapshot.newest < prevSnapshot.newest
            ? UpDownType.Down
            : UpDownType.Flat;

      const chgDiff = new Decimal(snapshot.chg).sub(new Decimal(prevSnapshot.chg))?.toNumber();

      const details =
        prevSnapshot.surplus > 0
          ? prevSnapshot.details.slice(prevSnapshot.details?.length - 1).concat(snapshot.details)
          : snapshot.details;

      const genClosingPosition = (snapshot: any, price: any, target: any, volume: any) => {
        const exp = /(S|B)\d{1}P/g;

        Object.keys(snapshot).forEach((key) => {
          if (exp.test(key) && snapshot[key] === price) {
            if (!target[key]) {
              target[key] = { key, volume, price };
            } else {
              target[key].volume += volume;
            }
          }
        });
      };
      const prevClosingPosition: any = {},
        closingPosition: any = {};

      details.forEach(({ price, volume }: any) => {
        genClosingPosition(prevSnapshot, price, prevClosingPosition, volume);
        genClosingPosition(snapshot, price, closingPosition, volume);
      });

      Object.keys(snapshot.diff.still).forEach((key: string) => {
        const { count } = snapshot.diff.still[key];

        if (count < 0) {
          if (closingPosition[key]) {
            const { volume } = closingPosition[key];

            if (volume > Math.abs(count)) {
              snapshot.diff.still[key].success = Math.abs(count);
            } else {
              snapshot.diff.still[key].success = volume;
              snapshot.diff.still[key].cancel = Math.abs(count) - volume;
            }
          } else {
            snapshot.diff.still[key].cancel = count;
          }
        }
      });

      const variableMap: any = Object.assign({}, snapshot.diff.still, snapshot.diff.newly);

      ++this.count;

      const base = snapshot.volume / this.count;

      let buyIncrement: number = 0; // 买单增量
      let sellIncrement: number = 0; // 卖单增量
      let buyWithdraw: number = 0; // 买方撤单
      let sellWithdraw: number = 0; // 卖方撤单

      Object.keys(variableMap).forEach((key: string) => {
        const { count, cancel } = variableMap[key];

        if (count > 0 && key.startsWith('S')) {
          sellIncrement += count;
        }

        if (cancel < 0 && key.startsWith('S')) {
          sellWithdraw += Math.abs(cancel);
        }

        if (count > 0 && key.startsWith('B')) {
          buyIncrement += count;
        }

        if (cancel < 0 && key.startsWith('B')) {
          buyWithdraw += Math.abs(cancel);
        }
      });

      //  let attractingIndex: number = 0; // 吸筹指数
      //  let measurementIndex: number = 0; // 衡量指数

      //  const { ITInner, ITOuter } = snapshot;
      const SExp = /S|\d/g,
        BExp = /B|\d/g;
      const calcPressure = (snapshot: any, exp: any) => {
        // 去除买五、卖五的影响，原因：买五或卖五的价格可能已存在，随着价格变更，会造成指数重复计算，导致误差系数变大
        // 压单系数的目的是计算出代表买卖方的意愿
        let coe: number = 0;

        const { diff } = snapshot;
        const { still, newly } = diff;

        Object.keys(still).forEach((key: string) => {
          const matches = key.match(exp);

          if (matches?.length === 2 && !['4', '5'].includes(matches?.[1])) {
            if (still[key]?.count > 0) {
              coe += (still[key]?.count / base) * RankMultipleMap[matches[1]];
            }
          }
        });
        Object.keys(newly).forEach((key: string) => {
          const matches = key.match(exp);

          if (matches?.length === 2 && !['4', '5'].includes(matches?.[1])) {
            if (newly[key]?.count > 0) {
              coe += (newly[key]?.count / base) * RankMultipleMap[matches[1]] * 2;
            }
          }
        });

        return coe;
      };
      const sellPressure: number = calcPressure(snapshot, SExp); // 卖单压单指数
      const buyPressure: number = calcPressure(snapshot, BExp); // 买单压单指数

      if (sellPressure / (sellPressure + buyPressure) >= 0.7) {
        if (sellPressure >= 10) {
          this.shippingOrder10TCount += 1;
        } else if (sellPressure >= 5) {
          this.shippingOrderCount += 1;
        }
      }

      if (buyPressure / (sellPressure + buyPressure) >= 0.7) {
        if (buyPressure >= 10) {
          this.billOfLading10TCount += 1;
        } else if (buyPressure >= 5) {
          this.billOfLadingCount += 1;
        }
      }

      // 如果股价上涨
      if (upDownType === UpDownType.Up) {
      } else if (upDownType === UpDownType.Down) {
      } else {
      }

      return {
        upDownType,
        buyIncrement,
        sellIncrement,
        buyWithdraw,
        sellWithdraw,
        chgDiff,
        //  attractingIndex,
        sellPressure: numeral(sellPressure).format('0.00'),
        buyPressure: numeral(buyPressure).format('0.00'),
        //  measurementIndex,
      };
    }

    return {};
  }

  statistics() {
    const snapshots = this.getMarketSnapshot().filter(({ Hms }) => {
      if (Hms >= '09:30:00' && Hms <= '11:30:00') {
        return true;
      } else if (Hms >= '13:00:00' && Hms <= '15:00:00') {
        return true;
      }

      return false;
    });

    let maxPressureSIndex: any = 0,
      maxPressureBIndex: any = 0,
      avgPressureSIndex: any = 0,
      avgPressureBIndex: any = 0,
      sumPressureSIndex: any = 0,
      sumPressureBIndex: any = 0;

    snapshots?.forEach((snapshot: any) => {
      const { sellPressure, buyPressure } = snapshot;

      maxPressureSIndex = Math.max(sellPressure || 0, maxPressureSIndex);
      maxPressureBIndex = Math.max(buyPressure || 0, maxPressureBIndex);
      sumPressureSIndex += sellPressure || 0;
      sumPressureBIndex += buyPressure || 0;
    });
    avgPressureSIndex = sumPressureSIndex / snapshots?.length;
    avgPressureBIndex = sumPressureBIndex / snapshots?.length;

    return {
      maxPressureSIndex,
      sumPressureSIndex,
      avgPressureSIndex,
      maxPressureBIndex,
      sumPressureBIndex,
      avgPressureBIndex,
      chg: snapshots.at(-1)?.chg,
      change: snapshots.at(-1)?.change,
    };
  }

  // 分析意图
  analyzeIntent(prevSnapshot: any, snapshot: any) {}
}
